/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
 * Description: UDK ring source file
 * Author: -
 * Create: 2021.5.6
 */
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>

#include "securec.h"
#include "udk_log.h"
#include "udk_tailq.h"
#include "udk_malloc.h"
#include "udk_atomic.h"
#include "udk_mcfg.h"
#include "udk_ops.h"
#include "udk_ring.h"

#define RING_F_EXACT_SZ     0x0004
#define UDK_RING_SZ_MASK    (0x7fffffffU) /* Ring size mask */

/* true if x is a power of 2 */
#define POWEROF2(x) ((((x) - 1) & (x)) == 0)

TAILQ_HEAD(udk_ring_list, udk_tailq_entry);
static struct udk_tailq_elem g_udk_ring_tailq = {
    .name = UDK_TAILQ_RING_NAME,
};
UDK_REGISTER_TAILQ(g_udk_ring_tailq)

static ssize_t udk_ring_get_memsize(uint32_t count)
{
    ssize_t sz;

    /* count must be a power of 2 */
    if (!POWEROF2(count) || (count > UDK_RING_SZ_MASK)) {
        UDK_LOG(ERR, RING, "Requested size is invalid, must be power of 2, and "
            "do not exceed the size limit %u\n", UDK_RING_SZ_MASK);
        return -EINVAL;
    }

    sz = (ssize_t)(sizeof(struct udk_ring) + count * sizeof(void *));
    sz = UDK_ALIGN(sz, UDK_CACHE_LINE_SIZE);
    return sz;
}

static int udk_ring_init(struct udk_ring *r, const char *name, uint32_t count, uint32_t flags)
{
    int ret;

    /* compilation-time checks */
    UDK_BUILD_BUG_ON((sizeof(struct udk_ring) & UDK_CACHE_LINE_MASK) != 0);
    UDK_BUILD_BUG_ON((offsetof(struct udk_ring, cons) & UDK_CACHE_LINE_MASK) != 0);
    UDK_BUILD_BUG_ON((offsetof(struct udk_ring, prod) & UDK_CACHE_LINE_MASK) != 0);

    (void)memset_s(r, sizeof(*r), 0, sizeof(*r));
    ret = snprintf_s(r->name, sizeof(r->name), sizeof(r->name) - 1, "%s", name);
    if (ret < 0) {
        return -ENAMETOOLONG;
    }

    r->flags = flags;
    r->prod.single = (flags & RING_F_SP_ENQ) ? IS_SP : IS_MP;
    r->cons.single = (flags & RING_F_SC_DEQ) ? IS_SC : IS_MC;

    if ((flags & RING_F_EXACT_SZ) != 0) {
        r->size = udk_align32pow2(count + 1);
        r->mask = r->size - 1;
        r->capacity = count;
    } else {
        if (!POWEROF2(count) || (count > UDK_RING_SZ_MASK)) {
            UDK_LOG(ERR, RING, "Requested size is invalid, must be power of 2, and "
                "do not exceed the size limit %u\n", UDK_RING_SZ_MASK);
            return -EINVAL;
        }
        r->size = count;
        r->mask = r->size - 1;
        r->capacity = r->mask;
    }

    if ((flags & RING_F_LOCK) != 0) {
        (void)pthread_mutex_init(&r->lock, NULL);
    }

    r->cons.head = 0;
    r->cons.tail = 0;
    r->prod.head = 0;
    r->prod.tail = 0;

    return 0;
}

struct udk_ring *udk_ring_create(const char *name, uint32_t count, int32_t socket_id, uint32_t flags)
{
    if (g_udk_reg_ops.ring_create != NULL) {
        return g_udk_reg_ops.ring_create(name, count, socket_id, flags);
    }
    char mz_name[UDK_MEMZONE_NAMESIZE];
    struct udk_ring *r = NULL;
    struct udk_tailq_entry *te = NULL;
    const struct udk_memzone *mz = NULL;
    ssize_t ring_size;
    struct udk_ring_list *ring_list = NULL;
    const uint32_t requested_count = count;
    int ret;

    ring_list = UDK_TAILQ_CAST(g_udk_ring_tailq.head, udk_ring_list);

    if ((flags & RING_F_EXACT_SZ) != 0) {
        count = udk_align32pow2(count + 1);
    }

    ring_size = udk_ring_get_memsize(count);
    if (ring_size < 0) {
        return NULL;
    }

    ret = snprintf_s(mz_name, sizeof(mz_name), sizeof(mz_name) - 1, "%s%s", UDK_RING_MZ_PREFIX, name);
    if (ret < 0) {
        UDK_LOG(ERR, RING, "sprintf ring mz name fail\n");
        return NULL;
    }

    te = udk_zmalloc(sizeof(*te), 0);
    if (te == NULL) {
        UDK_LOG(ERR, RING, "Cannot reserve memory for tailq\n");
        return NULL;
    }

    udk_mcfg_tailq_write_lock();

    mz = udk_memzone_reserve_aligned(mz_name, ring_size, socket_id, 0, __alignof__(struct udk_ring));
    if (mz != NULL) {
        r = mz->addr;
        (void)udk_ring_init(r, name, requested_count, flags);

        te->data = (void *)r;
        r->memzone = mz;

        TAILQ_INSERT_TAIL(ring_list, te, next);
    } else {
        r = NULL;
        UDK_LOG(ERR, RING, "Cannot reserve memory for ring memzone\n");
        udk_free(te);
    }

    udk_mcfg_tailq_write_unlock();

    return r;
}

struct udk_ring *udk_ring_lookup(const char *name)
{
    if (g_udk_reg_ops.ring_lookup != NULL) {
        return g_udk_reg_ops.ring_lookup(name);
    }
    struct udk_tailq_entry *te = NULL;
    struct udk_ring *r = NULL;
    struct udk_ring_list *ring_list;

    ring_list = UDK_TAILQ_CAST(g_udk_ring_tailq.head, udk_ring_list);

    udk_mcfg_tailq_read_lock();

    TAILQ_FOREACH(te, ring_list, next) {
        r = (struct udk_ring *)te->data;
        if (strncmp(name, r->name, UDK_RING_NAMESIZE) == 0) {
            break;
        }
    }

    udk_mcfg_tailq_read_unlock();

    if (te == NULL) {
        errno = ENOENT;
        return NULL;
    }

    return r;
}

void udk_ring_free(struct udk_ring *r)
{
    if (g_udk_reg_ops.ring_free != NULL) {
        return g_udk_reg_ops.ring_free(r);
    }
    struct udk_ring_list *ring_list = NULL;
    struct udk_tailq_entry *te = NULL;

    if (r == NULL) {
        return;
    }

    if (r->memzone == NULL) {
        UDK_LOG(ERR, RING, "Cannot free ring, not created with udk_ring_create()\n");
        return;
    }

    if (udk_memzone_free(r->memzone) != 0) {
        UDK_LOG(ERR, RING, "Cannot free memory\n");
        return;
    }

    ring_list = UDK_TAILQ_CAST(g_udk_ring_tailq.head, udk_ring_list);
    udk_mcfg_tailq_write_lock();

    /* find out tailq entry */
    TAILQ_FOREACH(te, ring_list, next) {
        if (te->data == (void *)r) {
            break;
        }
    }

    if (te == NULL) {
        udk_mcfg_tailq_write_unlock();
        return;
    }

    TAILQ_REMOVE(ring_list, te, next);

    udk_mcfg_tailq_write_unlock();

    udk_free(te);
    return;
}

static udk_force_inline uint32_t udk_ring_enqueue_bulk(struct udk_ring *r, void * const *obj_table, uint32_t n,
    uint32_t *free_space)
{
    return udk_ring_do_enqueue(r, obj_table, n, UDK_RING_QUEUE_FIXED, r->prod.single, free_space);
}

static udk_force_inline uint32_t udk_ring_dequeue_bulk(struct udk_ring *r, void **obj_table, uint32_t n,
    uint32_t *available)
{
    return udk_ring_do_dequeue(r, obj_table, n, UDK_RING_QUEUE_FIXED, r->cons.single, available);
}

int udk_ring_dequeue(struct udk_ring *r, void **obj_p)
{
    if (g_udk_reg_ops.ring_dequeue != NULL) {
        return g_udk_reg_ops.ring_dequeue(r, obj_p);
    }
    return udk_ring_dequeue_bulk(r, obj_p, 1, NULL) ? 0 : -ENOENT;
}

int udk_ring_enqueue(struct udk_ring *r, void *obj)
{
    if (g_udk_reg_ops.ring_enqueue != NULL) {
        return g_udk_reg_ops.ring_enqueue(r, obj);
    }
    return udk_ring_enqueue_bulk(r, &obj, 1, NULL) ? 0 : -ENOBUFS;
}

uint32_t udk_ring_count(const struct udk_ring *r)
{
    if (g_udk_reg_ops.ring_count != NULL) {
        return g_udk_reg_ops.ring_count(r);
    }
    uint32_t count;

    count = (r->prod.tail - r->cons.tail) & r->mask;
    return (count > r->capacity) ? r->capacity : count;
}

uint32_t udk_ring_free_count(const struct udk_ring *r)
{
    if (g_udk_reg_ops.ring_free_count != NULL) {
        return g_udk_reg_ops.ring_free_count(r);
    }
    return r->capacity - udk_ring_count(r);
}

int udk_ring_full(struct udk_ring *r)
{
    if (g_udk_reg_ops.ring_full != NULL) {
        return g_udk_reg_ops.ring_full(r);
    }
    return udk_ring_free_count(r) == 0;
}

void udk_ring_dump(FILE *f, const struct udk_ring *r)
{
    if (g_udk_reg_ops.ring_dump != NULL) {
        return g_udk_reg_ops.ring_dump(f, r);
    }
    (void)fprintf(f, "ring <%s>@0x%lx\n", r->name, udk_get_ptr(r));
    (void)fprintf(f, "  flags=%x\n", r->flags);
    (void)fprintf(f, "  size=%u\n", r->size);
    (void)fprintf(f, "  capacity=%u\n", r->capacity);
    (void)fprintf(f, "  ct=%u\n", r->cons.tail);
    (void)fprintf(f, "  ch=%u\n", r->cons.head);
    (void)fprintf(f, "  pt=%u\n", r->prod.tail);
    (void)fprintf(f, "  ph=%u\n", r->prod.head);
    (void)fprintf(f, "  used=%u\n", udk_ring_count(r));
    (void)fprintf(f, "  avail=%u\n", udk_ring_free_count(r));

    return;
}
